/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.geode.distributed.internal.membership.gms.messenger;

import org.apache.geode.distributed.internal.DistributionConfig;
import org.apache.geode.distributed.internal.membership.InternalDistributedMember;
import org.apache.geode.distributed.internal.membership.gms.GMSMember;
import org.apache.geode.internal.net.SocketCreator;
import org.jgroups.Global;
import org.jgroups.stack.IpAddress;
import org.jgroups.util.UUID;

import java.io.*;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;

/**
 * This is a copy of JGroups 3.6.4 IpAddress (Apache 2.0 License)
 * that is repurposed to be a Logical address so that we can
 * quickly pull a physical address out of the logical address set
 * in a message by JGroupsMessenger.
 */

public class JGAddress extends UUID {
  private static final long       serialVersionUID=-1818672332115113291L;

  // whether to show UUID info in toString()
  private final static boolean SHOW_UUIDS = Boolean.getBoolean(DistributionConfig.GEMFIRE_PREFIX + "show_UUIDs");

  private InetAddress             ip_addr;
  private int                     port;
  private int                     vmViewId;


  // Used only by Externalization
  public JGAddress() {
  }

  public JGAddress(InternalDistributedMember idm) {
    super();
    GMSMember mbr = (GMSMember)idm.getNetMember();
    this.mostSigBits = mbr.getUuidMSBs();
    this.leastSigBits = mbr.getUuidLSBs();
    this.ip_addr = idm.getInetAddress();
    this.port = idm.getPort();
    this.vmViewId = idm.getVmViewId();
  }


  public JGAddress(GMSMember mbr) {
    super();
    this.mostSigBits = mbr.getUuidMSBs();
    this.leastSigBits = mbr.getUuidLSBs();
    this.ip_addr = mbr.getInetAddress();
    this.port = mbr.getPort();
    this.vmViewId = mbr.getVmViewId();
  }


  public JGAddress(UUID uuid, IpAddress ipaddr) {
    super(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits());
    ip_addr=ipaddr.getIpAddress(); port=ipaddr.getPort(); vmViewId=-1;
  }


  public final InetAddress  getInetAddress()               {return ip_addr;}
  public final int          getPort()                    {return port;}
  
  public final int getVmViewId() {
    return this.vmViewId;
  }
  
  protected final void setVmViewId(int id) {
    this.vmViewId = id;
  }


  @Override
  public String toString() {
      StringBuilder sb=new StringBuilder();

      if(ip_addr == null)
          sb.append("<null>");
      else {
        if (SocketCreator.resolve_dns) {
          sb.append(SocketCreator.getHostName(ip_addr));
        } else {
          sb.append(ip_addr.getHostAddress());
        }
      }
      if (vmViewId >= 0) {
        sb.append("<v").append(vmViewId).append('>');
      }
      if (SHOW_UUIDS) {
        sb.append("(").append(toStringLong()).append(")");
      } else if (mostSigBits == 0 && leastSigBits == 0) {
        sb.append("(no uuid set)");
      }
      sb.append(":").append(port);
      return sb.toString();
  }


  @Override
  public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
      try {
          readFrom(in);
      }
      catch(Exception e) {
          throw new IOException(e);
      }
  }

  @Override
  public void writeExternal(ObjectOutput out) throws IOException {
      try {
          writeTo(out);
      }
      catch(Exception e) {
          throw new IOException(e);
      }
  }

  @Override
  public void writeTo(DataOutput out) throws Exception {
      if(ip_addr != null) {
          byte[] address=ip_addr.getAddress();  // 4 bytes (IPv4) or 16 bytes (IPv6)
          out.writeByte(address.length); // 1 byte
          out.write(address, 0, address.length);
          if(ip_addr instanceof Inet6Address)
              out.writeInt(((Inet6Address)ip_addr).getScopeId());
      }
      else {
          out.writeByte(0);
      }
      out.writeShort(port);
      out.writeInt(vmViewId);
      out.writeLong(mostSigBits);
      out.writeLong(leastSigBits);
  }
  
  public long getUUIDMsbs() {
    return mostSigBits;
  }
  
  public long getUUIDLsbs() {
    return leastSigBits;
  }

  @Override
  public void readFrom(DataInput in) throws Exception {
      int len=in.readByte();
      if(len > 0 && (len != Global.IPV4_SIZE && len != Global.IPV6_SIZE))
          throw new IOException("length has to be " + Global.IPV4_SIZE + " or " + Global.IPV6_SIZE + " bytes (was " +
                  len + " bytes)");
      byte[] a = new byte[len]; // 4 bytes (IPv4) or 16 bytes (IPv6)
      in.readFully(a);
      if(len == Global.IPV6_SIZE) {
          int scope_id=in.readInt();
          this.ip_addr=Inet6Address.getByAddress(null, a, scope_id);
      }
      else {
          this.ip_addr=InetAddress.getByAddress(a);
      }

      // changed from readShort(): we need the full 65535, with a short we'd only get up to 32K !
      port=in.readUnsignedShort();
      vmViewId = in.readInt();
      mostSigBits = in.readLong();
      leastSigBits = in.readLong();
  }

  @Override
  public int size() {
      // length (1 bytes) + 4 bytes for port
      int tmp_size=Global.BYTE_SIZE+ Global.SHORT_SIZE +Global.SHORT_SIZE
          + (2*Global.LONG_SIZE);
      if(ip_addr != null) {
          // 4 bytes for IPv4, 20 for IPv6 (16 + 4 for scope-id)
          tmp_size+=(ip_addr instanceof Inet4Address)? 4 : 20;
      }
      return tmp_size;
  }

  @Override
  public JGAddress copy() {
    JGAddress result = new JGAddress();
    result.mostSigBits = mostSigBits;
    result.leastSigBits = leastSigBits;
    result.ip_addr = ip_addr;
    result.port = port;
    result.vmViewId = vmViewId;
    return result;
  }

  public IpAddress asIpAddress() {
    return new IpAddress(ip_addr, port);
  }
  
}
